package pers.zb.cloud.service.upms.aop.transaction_manager;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import pers.zb.cloud.service.upms.aop.datasource.DS;
import pers.zb.cloud.service.upms.aop.datasource.DataSourceConfig;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

/**
 * 实现事务管理器的动态设置
 *      1、【为何要在这个项目中加入事务管理器动态设置的功能？】
 *          因为当前项目中，有一个客户端管理的功能（ClientController），数据是使用与sea-cloud-oauth项目相同的数据库，故采用了动态数据源。
 *          同时，因为出现了不同的数据库，也就出现了不同的数据源，当需要进行更新数据的时候，需要使用到 @Transactional 注解，注解中需要通过 value 来设置具体使用哪一个事务管理器。
 *          所以，硬编码配置事务管理器也是可以的，但使用AOP来动态设置是一个更好的方式，比较解耦合。
 *      2、【事务管理器动态设置的思路逻辑？】
 *          会检测业务方法是否具有 @Transactional 注解，如果有的话，且没有@DS 的注解，就默认设置当前系统（upms系统）的事务管理器；
 *          如果有 @DS 注解的话，根据 @DS 注解指定的数据源名称，获取对应的事务管理器进行设置。
 * 【通过AOP实现事务管理器的动态设置的优点】：
 *      一般我们在业务方法上（一般都是 service 的实现类的方法），都是直接使用 @Transactional 注解，并未设置 value 值，因为项目一般都是连接单个数据源，所以也就只有一个事务管理器，所以不会出现问题。
 *      但当前项目使用了多个数据源，也就对应着多个事务管理器，如果硬编码的话，业务方法的注解应该是这样，例如： @Transactional(value="upms_transactionManager")，这样就需要开发者额外配置value的值，很不友好，
 *      为了不侵入代码，所以就使用AOP来实现@Transactional注解里面的value值的动态设置，不同的数据源对应着不同的事务管理器，就实现了代码解耦，不侵入代码，平时该怎么写代码和配置注解，还是怎么写，实现透明化。
 */
@Slf4j
@Aspect
@Component
public class DynamicTransactionManagerSettingAspect {

    /**
     * 前置处理
     */
    @Before("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void beforeSwitchTransactional(JoinPoint point){
        Class<?> className = point.getTarget().getClass();//获得当前访问的class
        String methodName = point.getSignature().getName();//获得访问的方法名
        Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();//得到方法的参数的类型
        String transactionManagerName = "";//事务管理器名称
        try {
            Method method = className.getMethod(methodName, argClass);// 得到访问的方法对象
            if (method.isAnnotationPresent(Transactional.class)) {// 判断是否存在@Transactional注解
                Transactional transactional = method.getAnnotation(Transactional.class);
                transactionManagerName = transactional.value();// 获取注解中的事务管理器名称
                //log.debug("事务管理器名称原来的值：" + transactionManagerName);

                DS ds = method.getAnnotation(DS.class);
                // 判断是否存在@DS注解
                if (method.isAnnotationPresent(DS.class)) {
                    String dsName =  ds.value().getName();//数据源名称
                    transactionManagerName = DataSourceConfig.TRANSACTION_MANAGERMAP.get(dsName);
                }else{
                    //log.debug("事务方法没有@DS注解。。。");
                    transactionManagerName = DataSourceConfig.DEFAULT_TRANSACTION_MANAGER_NAME;
                }
                //改变注解中的value值
                changeTransactionalAnnotationValue(transactional,"value",transactionManagerName);

                String newValue = transactional.value();
                //log.debug("事务管理器名称最新的值：" + newValue);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 后置处理
     */
    @After("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void afterSwitchTransactional(JoinPoint point){
        //System.out.println("后置通知......");
    }

    /**
     * 改变注解中的value值
     */
    public static Object changeTransactionalAnnotationValue(Annotation transactionalAnnotation, String key, Object newValue) throws Exception{
        //获取 transactional 这个代理实例所持有的 InvocationHandler
        InvocationHandler handler = Proxy.getInvocationHandler(transactionalAnnotation);
        Field field;
        try {
            field = handler.getClass().getDeclaredField("memberValues");
        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalStateException(e);
        }
        //因为这个field字段是 private final 修饰，所以要打开权限
        field.setAccessible(true);
        Map<String, Object> memberValues;
        memberValues = (Map<String, Object>) field.get(handler);
        Object oldValue = memberValues.get(key);
        if(oldValue == null || oldValue.getClass() != newValue.getClass()){
            throw new IllegalArgumentException();
        }
        memberValues.put(key, newValue);
        return oldValue;
    }
}